#pragma once

#include "allocator.hpp"
#include "log_functor.hpp"
#include "reverse_iterator.hpp"

namespace zuck
{
	template<typename T, size_t _size>
	class _ArrayConstIterator {
	public:
		using value_type		= T;
		using pointer			= const T*;
		using reference			= const T&;
		using difference_type   = ptrdiff_t;
		using iterator_category = zuck::random_access_iterator_tag;
	private:
		using _Self = _ArrayConstIterator<T, _size>;
	private:
		T*     _m_first;
		size_t _m_ind;
	public:
		constexpr _ArrayConstIterator(value_type* first, size_t const ind)
			: _m_first(first), _m_ind(ind) {}

		constexpr _ArrayConstIterator(const _Self& other)
			: _m_first(other._m_first), _m_ind(other._m_ind) {}

		constexpr _ArrayConstIterator(_Self&& other)noexcept
			: _m_first(other._m_first), _m_ind(other._m_ind)
		{
			other._m_first = null;
			other._m_ind   = 0ull;
		}

		ZCONSTEXPR ~_ArrayConstIterator()
		{
			_m_first = null;
			_m_ind   = 0ull;
		}

		[[nodiscard]] constexpr pointer get_unwrapped()const noexcept
		{ return _m_first[_m_ind]; }

		constexpr _ArrayConstIterator& operator=(const _Self& other)
		{
			_m_first = other._m_first;
			_m_ind	 = other._m_ind;
			return *this;
		}

		constexpr _ArrayConstIterator& operator=(_Self&& other)noexcept
		{
			_m_first = other._m_first;
			_m_ind	 = other._m_ind;
			other._m_first = null;
			other._m_ind   = 0ull;
			return *this;
		}

		[[nodiscard]] constexpr reference operator*()const noexcept
		{
			if constexpr (debug_) {
				if (_m_ind > _size) {
					debug_log::derror(zuck::SEG_WrongIter, 
									"Segmentfault: invalid pointer");
				}
			}
			return _m_first[_m_ind];
		}

		[[nodiscard]] constexpr pointer operator->()const noexcept
		{
			if constexpr (debug_) {
				if (_m_ind > _size) {
					debug_log::derror(zuck::SEG_WrongIter, 
									"Segmentfault: invalid pointer");
				}
			}
			return _m_first[_m_ind];
		}

		constexpr _Self& operator++()noexcept
		{
			if constexpr (debug_) {
				if (_m_ind > _size) {
					debug_log::derror(zuck::SEG_WrongIter, 
									"Segmentfault: invalid pointer");
				}
			}
			++_m_ind;
			return *this;
		}

		constexpr _Self operator++(int)noexcept
		{
			auto _Tmp = *this;
			++(*this);
			return _Tmp;
		}

		constexpr _Self& operator+=(const difference_type step)noexcept
		{
			if constexpr (debug_) {
				if (step >= _m_ind) {
					debug_log::derror(zuck::SEG_OutofRange, 
						              "Segmentfault: step out of range !");
				}
			}
			_m_ind += step;
			return *this;
		}

		[[nodiscard]] constexpr _Self operator+(const difference_type step)const noexcept
		{
			auto _Tmp = *this;
			return _Tmp += step;
		}

		[[nodiscard]] constexpr reference operator[](const difference_type ind)const noexcept
		{
			return *(*this + ind);
		}

		constexpr _Self& operator--()noexcept
		{
			if constexpr (debug_) {
				if (_m_first + _m_ind < _m_first + _size) {
					debug_log::derror(zuck::SEG_WrongIter, 
						              "Segmentfault: invalid pointer");
				}
			}
			--_m_ind;
			return *this;
		}

		constexpr _Self operator--(int)noexcept
		{
			auto _Tmp = *this;
			--(*this);
			return _Tmp;
		}

		constexpr _Self& operator-=(const difference_type _Step)noexcept
		{
			if constexpr (debug_) {
				if (_m_first - _Step < _m_first) {
					debug_log::derror(zuck::SEG_OutofRange, 
						              "Segmentfault: step out of range !");
				}
			}
			_m_ind -= _Step;
			return *this;
		}

		[[nodiscard]] constexpr _Self operator-(const difference_type _Step)const noexcept
		{
			auto _Tmp = *this;
			return _Tmp -= _Step;
		}

		[[nodiscard]] constexpr difference_type operator-(const _Self& other)const noexcept
		{
			return static_cast<difference_type>(_m_ind - other._m_ind);
		}

		[[nodiscard]] constexpr difference_type operator-(const T* const ptr)const noexcept
		{
			return static_cast<difference_type>((_m_first + _m_ind) - ptr);
		}

		[[nodiscard]] constexpr bool operator==(const _Self& other)const noexcept
		{
			return static_cast<bool>(_m_ind == other._m_ind);
		}

		[[nodiscard]] constexpr bool operator!=(const _Self& other)const noexcept
		{
			return static_cast<bool>(_m_ind != other._m_ind);
		}

		[[nodiscard]] constexpr bool operator<(const _Self& other)const noexcept
		{
			return static_cast<bool>(_m_ind <= other._m_ind);
		}

		[[nodiscard]] constexpr bool operator<=(const _Self& other)const noexcept
		{
			return static_cast<bool>(_m_ind <= other._m_ind);
		}

		[[nodiscard]] constexpr bool operator>(const _Self& other)const noexcept
		{
			return static_cast<bool>(_m_ind > other._m_ind);
		}

		[[nodiscard]] constexpr bool operator>=(const _Self& other)const noexcept
		{
			return static_cast<bool>(_m_ind >= other._m_ind);
		}
	};

	template<typename T, zuck::size_t _size>
	class _ArrayIterator : public _ArrayConstIterator<T, _size> {
	private:
		using _Base = _ArrayConstIterator<T, _size>;
		using _Self = _ArrayIterator<T, _size>;
	public:
		using value_type		= T;
		using pointer			= T*;
		using reference			= T&;
		using difference_type	= zuck::ptrdiff_t;
		using iterator_category = random_access_iterator_tag;
	public:
		explicit constexpr _ArrayIterator(pointer first, size_t const size)
			: _Base(first, size) {}

		constexpr _ArrayIterator(const _Self& other)
			: _Base(other) {}

		constexpr _ArrayIterator(_Self&& other)noexcept
			: _Base(zuck::move(other)) {}

		constexpr _Self& operator=(const _Self& other)
		{
			_Base::operator=(other);
			return *this;
		}

		constexpr _Self& operator=(_Self&& other)noexcept
		{
			_Base::operator=(zuck::move(other));
			return *this;
		}

		[[nodiscard]] constexpr reference operator*()const noexcept
		{
			return const_cast<reference>(_Base::operator*());
		}

		[[nodiscard]] constexpr pointer operator->()const noexcept
		{
			return const_cast<pointer>(_Base::operator->());
		}

		constexpr _Self& operator++()noexcept
		{
			_Base::operator++();
			return *this;
		}

		constexpr _Self operator++(int)noexcept
		{
			auto _Tmp = *this;
			_Base::operator++();
			return _Tmp;
		}

		constexpr _Self& operator--()noexcept
		{
			_Base::operator--();
			return *this;
		}

		constexpr _Self operator--(int)noexcept
		{
			auto _Tmp = *this;
			_Base::operator--();
			return _Tmp;
		}

		constexpr _Self& operator+=(const difference_type _Step)noexcept
		{
			_Base::operator+=(_Step);
			return *this;
		}

		[[nodiscard]] constexpr _Self operator+(const difference_type _Step)const
		{
			auto _Tmp = *this;
			return _Tmp += _Step;
		}

		[[nodiscard]] constexpr reference operator[](const difference_type _Ind)const noexcept
		{
			return const_cast<reference>(_Base::operator[](_Ind));
		}

		constexpr _Self& operator-=(const difference_type _Step)
		{
			_Base::operator-(_Step);
			return *this;
		}

		[[nodiscard]] constexpr _Self operator-(const difference_type _Step)const
		{
			auto _Tmp = *this;
			return _Tmp -= _Step;
		}

		[[nodiscard]] constexpr difference_type operator-(const _Self& other)const noexcept
		{
			return _Base::operator-(other);
		}

		[[nodiscard]] constexpr difference_type operator-(const T* const ptr)const noexcept
		{
			return _Base::operator-(ptr);
		}
	};


	template<typename T, zuck::size_t _size>
	class array {
		static_assert(
			!zuck::is_same_v<T, const T> || !zuck::is_same_v<T, volatile T>,
			"template parameter can not const or volatile");
	private:
		using _Self     = array<T, _size>;
		using _Alty_tts = zuck::allocator_traits<zuck::allocator<T>>;
	public:
		using value_type        = T;
		using pointer			= T*;
		using const_pointer		= const T*;
		using reference			= T&;
		using const_reference	= const T&;
		using rvalue_reference	= T&&;
		using size_type			= zuck::size_t;
		using difference_type	= zuck::ptrdiff_t;
	public:
		using iterator				 = _ArrayIterator<T, _size>;
		using const_iterator		 = _ArrayConstIterator<T, _size>;
		using reverse_iterator		 = zuck::reverse_iterator<iterator>;
		using const_reverse_iterator = zuck::reverse_iterator<const_iterator>;
	private:
		T _m_dataArray[_size]{};
		size_type _m_ind;
	public:
		explicit constexpr array() : _m_ind() {}

		explicit constexpr array(const size_type count, const T& val) : _m_ind()
		{
			for (; _m_ind < count; ++_m_ind) {
				_m_dataArray[_m_ind] = val;
			}
		}

		constexpr array(std::initializer_list<T> data) : _m_ind()
		{
			__construct_array(data.begin(), data.end());
		}

		constexpr array(const array& other) : _m_ind(0)
		{
			__construct_array(other.begin(), other.end());
		}

		constexpr array(array&& other)noexcept : _m_ind(0)
		{
			__move_construct(zuck::move(other));
		}

	public:
		constexpr void assign(const size_type _Newsize, const T& val)
		{
			__construct_n(_Newsize, val);
		}

		constexpr void assign(std::initializer_list<T> data)
		{
			__construct_range(data.begin(), data.end());
		}

		template<typename InputIterator>
		constexpr void assign(InputIterator Begin, InputIterator End)
		{
			__construct_range(Begin, End);
		}

		[[nodiscard]] constexpr reference at(const size_type _Idx)
		{
			if constexpr (debug_) {
				__verify_ind(_Idx);
			}
			return _m_dataArray[_Idx];
		}

		[[nodiscard]] constexpr const_reference at(const size_type _Idx)const
		{
			if constexpr (debug_) {
				__verify_ind(_Idx);
			}
			return _m_dataArray[_Idx];
		}

		[[nodiscard]] constexpr pointer data()noexcept
		{
			return _m_dataArray;
		}

		[[nodiscard]] constexpr const_pointer data()const noexcept
		{
			return _m_dataArray;
		}

		[[nodiscard]] constexpr reference front()noexcept
		{
			return _m_dataArray[0];
		}

		[[nodiscard]] constexpr const_reference front()const noexcept
		{
			return _m_dataArray[0];
		}

		[[nodiscard]] constexpr reference back()noexcept
		{
			return _m_dataArray[_m_ind - 1];
		}

		[[nodiscard]] constexpr const_reference back()const noexcept
		{
			return _m_dataArray[_m_ind - 1];
		}

		[[nodiscard]] constexpr size_type size()const noexcept
		{
			return _m_ind;
		}

		[[nodiscard]] constexpr size_type max_size()const noexcept
		{
			return _size;
		}

		constexpr void swap(array<T, _size>& other)noexcept
		{
			if (*this != other) {
				auto _Tmp = zuck::move(*this);
				*this	  = zuck::move(other);
				other	  = zuck::move(_Tmp);
			}
		}

		[[nodiscard]] constexpr iterator begin()noexcept
		{
			return iterator(const_cast<T*>(_m_dataArray), 0);
		}

		[[nodiscard]] constexpr iterator begin()const noexcept
		{
			return iterator(const_cast<T*>(_m_dataArray), 0);
		}

		[[nodiscard]] constexpr iterator end()noexcept
		{
			return iterator(const_cast<T*>(_m_dataArray), _m_ind);
		}

		[[nodiscard]] constexpr iterator end()const noexcept
		{
			return iterator(const_cast<T*>(_m_dataArray), _m_ind);
		}

		[[nodiscard]] constexpr const_iterator cbegin()const noexcept
		{
			return const_iterator(const_cast<T*>(_m_dataArray), 0);
		}

		[[nodiscard]] constexpr const_iterator cend()const noexcept
		{
			return const_iterator(const_cast<T*>(_m_dataArray), _m_ind);
		}

		[[nodiscard]] constexpr reverse_iterator rbegin()noexcept
		{
			return reverse_iterator(const_cast<T*>(_m_dataArray), _m_ind - 1);
		}

		[[nodiscard]] constexpr reverse_iterator rend()const noexcept
		{
			return reverse_iterator(const_cast<T*>(_m_dataArray), -1);
		}

		[[nodiscard]] constexpr const_reverse_iterator crbegin()const noexcept
		{
			return const_reverse_iterator(const_cast<T*>(_m_dataArray), _m_ind - 1);
		}

		[[nodiscard]] constexpr const_reverse_iterator crend()const noexcept
		{
			return const_reverse_iterator(const_cast<T*>(_m_dataArray), -1);
		}

		constexpr array& operator=(const array& other)
		{
			__construct_array(other.begin(), other.end());
			return *this;
		}

		constexpr array& operator=(array&& other)noexcept
		{
			__move_construct(zuck::move(other));
			return *this;
		}

		[[nodiscard]] constexpr reference operator[](const size_type _Idx)noexcept
		{
			if constexpr (debug_) {
				__verify_ind(_Idx);
			}
			return _m_dataArray[_Idx];
		}

		[[nodiscard]] constexpr const_reference operator[](const size_type _Idx)const noexcept
		{
			if constexpr (debug_) {
				__verify_ind(_Idx);
			}
			return _m_dataArray[_Idx];
		}

		[[nodiscard]] constexpr bool operator==(const _Self& other)const noexcept
		{
			if (size() != other.size()) {
				return false;
			}
			for (size_type i = 0; i < other.size(); ++i) {
				if (_m_dataArray[i] != other[i]) {
					return false;
				}
			}
			return true;
		}

		[[nodiscard]] constexpr bool operator!=(const _Self& other)const noexcept
		{
			if (size() != other.size()) {
				return true;
			}
			for (size_type i = 0; i < other.size(); ++i) {
				if (_m_dataArray[i] != other[i]) {
					return true;
				}
			}
			return false;
		}

	private:
		template<typename Iterator>
		constexpr void __construct_array(Iterator first, Iterator last)
		{
			auto _Newsize = zuck::distance(first, last);
			__verify_size(_Newsize);

			auto _First      = zuck::move(first);
			auto const _Last = zuck::move(last);

			for (; _Newsize && _First != _Last; 
				 ++_m_ind, ++_First, --_Newsize)
			{
				_m_dataArray[_m_ind] = *_First;
			}
		}

		constexpr void __move_construct(_Self&& other)
		{
			_Alty_tts::destruct_range(_m_dataArray, _m_dataArray + _m_ind);
			_m_ind = other._m_ind;
			zuck::fill(other.begin(), other.end(), begin(), end());
			_Alty_tts::destruct_range(other.begin(), other.end());
			other._m_ind = 0;
		}

		constexpr void __verify_size(const size_type _Newsize)
		{
			if (_Newsize > _size) {
				debug_log::derror(zuck::SEG_LengthTooLong, "_Newsize too long !");
			}
		}

		constexpr void __verify_ind(const size_type _Idx)
		{
			if (zuck::out_of_range(_Idx, _m_ind)) {
				debug_log::derror(zuck::SEG_OutofRange, "Segmentfault: subscript out of range");
			}
		}
	};

	template<typename T, zuck::size_t _Size>
	array(zuck::size_t, T)->array<T, _Size>;

	template<typename InputItertor, 
		typename T = zuck::iter_traits_t<InputItertor>, 
		zuck::size_t _Size>
	array(InputItertor, InputItertor) -> array<T, _Size>;

	template<typename T, zuck::size_t _Size>
	array(const array<T, _Size>&)->array<T, _Size>;

	template<typename T, zuck::size_t _Size>
	array(array<T, _Size>&&)->array<T, _Size>;


	template<typename T>
	class array<T, 0> {
	public:
		using value_type	   = T;
		using pointer		   = T*;
		using const_pointer	   = const T*;
		using reference		   = T&;
		using const_reference  = const T&;
		using size_type		   = zuck::size_t;
		using difference_type  = zuck::ptrdiff_t;
	public:
		using iterator				 = _ArrayIterator<T, 0>;
		using const_iterator		 = _ArrayConstIterator<T, 0>;
		using reverse_iterator		 = zuck::reverse_iterator<iterator>;
		using const_reverse_iterator = zuck::reverse_iterator<const_iterator>;
	public:
		[[nodiscard]] T* data()noexcept { return null; }

		[[nodiscard]] T const* data()const noexcept { null; }

		[[nodiscard]] size_type size()const noexcept { return 0; }

		[[nodiscard]] size_type max_size()const noexcept { return 0; }

		[[nodiscard]] iterator begin()const noexcept { return iterator{}; }

		[[nodiscard]] iterator end()const noexcept { return iterator{}; }

		[[nodiscard]] const_iterator cbegin()const noexcept { return const_iterator{}; }

		[[nodiscard]] const_iterator cend()const noexcept { return const_iterator{}; }

		[[nodiscard]] reference operator[](const size_type _Idx)noexcept { return *data(); }
	};

	// static array
	template<typename T, ::zuck::size_t __n>
	using narray = array<T, __n>;

	template<::zuck::size_t n>
	using i8_array = narray<i8_t, n>;

	template<::zuck::size_t n>
	using u8_array = narray<u8_t, n>;

	template<::zuck::size_t n>
	using i16_array = narray<i16_t, n>;

	template<::zuck::size_t n>
	using u16_array = narray<u16_t, n>;

	template<::zuck::size_t n>
	using i32_array = narray<i32_t, n>;

	template<::zuck::size_t n>
	using u32_array = narray<u32_t, n>;

	template<::zuck::size_t n>
	using i64_array = narray<i64_t, n>;

	template<::zuck::size_t n>
	using u64_array = narray<u64_t, n>;

	template<::zuck::size_t n>
	using f32_array = narray<f32_t, n>;

	template<::zuck::size_t n>
	using f64_array = narray<f64_t, n>;

#if __linux__
	template<::zuck::size_t n>
	using f128_array = narray<f128_t, n>;
#endif

}

